#include <iostream>
#include <string>
#include <cstring>
#include <sstream>

#include "row.h"

using namespace std;




Row::Row(string content){
    this->_content = new string();
    *(this->_content) = content; 
    update_len();
}

Row::~Row(){
    delete this->_content;
}


std::string Row::Render(int start,int end){
    int renderEnd = end < _content->length() ? end: _content->length();
    int renderStart = start < renderEnd ? start : renderEnd;
    stringstream ss;
    HighlightingType current_highlighting = HighlightingType::None;
    for(int i=renderStart;i<renderEnd;i++){
        char c = this->_content->at(i);
        HighlightingType hl_type = HighlightingType::None;
        if (this->_highlighting.size() > i){
           hl_type = this->_highlighting[i]; 
        }
        if (current_highlighting != hl_type){
            current_highlighting = hl_type;
            ss << Highlighting::ToColor(hl_type);
        }
        ss << this->_content->at(i);
    }
    ss << Highlighting::ResetColor();
    return ss.str();
}



int Row::Length(){
    return this->_len;
}

bool Row::IsEmpty(){
    return this->_len == 0;
}


void Row::update_len(){
    this->_len = this->_content->length();
}


void Row::Insert(int at,char c){
    if (at >= this->Length()){
        this->_content->push_back(c);
    } else {
        stringstream newStr;
        newStr << this->_content->substr(0,at) << c << this->_content->substr(at,this->_content->length()-at);
        *(this->_content) = newStr.str();
    }
    this->update_len();
}


void Row::Delete(int at){
    if (at >= this->Length()){
        return;
    } else {
        stringstream newStr;
        newStr << this->_content->substr(0,at) << this->_content->substr(at+1,this->_content->length()-at-1);
        *(this->_content) = newStr.str();
    }
    this->update_len();
}

void Row::Append(Row* newRow){
    this->_content->append(*(newRow->_content));
    this->update_len();
}


Row* Row::Split(int at){
    string begin = this->_content->substr(0,at);
    string remaider = this->_content->substr(at,this->Length() - at);
    *(this->_content) = begin;
    this->update_len();
    return new Row(remaider);

}


const char* Row::GetContent(){
    return this->_content->c_str();
}


int Row::Find(string str,int at,SearchDirection direction){
    if (at > this->Length()|| str.length()==0){
        return -1;
    }
    int start = 0;
    int end =at;
    if (direction == SearchDirection::Forward){
        start = at;
        end = this->Length();
        std::size_t rtn = this->_content->find(str,start);
        if (rtn != std::string::npos){
            return (int)rtn;
        } else {
            return -1;
        }
    } else {
        std::size_t rtn = this->_content->rfind(str,Util::SaturatingSub(end,1));
        if (rtn != std::string::npos){
            return (int)rtn;
        } else {
            return -1;
        }
    }
}


bool Row::Highlight(HighlightingOptions opts, string str,bool start_with_comment){
    if (this->IsHighlighted && str == "" ){
        if (this->_highlighting.size()>0){
            HighlightingType hl_type = this->_highlighting.back();
            if (hl_type == HighlightingType::MultilineComments
                && this->_content->length()>1
                && this->_content->substr(this->_content->length()-2)=="*/"){
                return true;
            }
        }
        return false;
    }
    if (*(this->_content) == " "){
        int dd = 12345;
    }

    const char* p_chars = this->_content->c_str();
    this->_highlighting.clear();
    int index = 0;
    bool in_ml_comment = start_with_comment;
    if (in_ml_comment){
        int close_index = str.length();
        size_t find_index = this->_content->find("*/");
        if (find_index!=string::npos){
            close_index = (int)find_index + 2;
        }
        for(int i=0;i<close_index;i++){
            this->_highlighting.push_back(HighlightingType::MultilineComments);
        }
        index = close_index;
    }
    while(index < strlen(p_chars)){
        char c = p_chars[index];
        bool multiComments = this->HighlightMultilineComments(&index,opts,c,p_chars);
        if (multiComments){
            in_ml_comment = true;
            continue;
        }
        in_ml_comment= false;
        if (this->HighlightChar(&index,opts,c,p_chars)
          ||this->HighlightComment(&index,opts,c,p_chars)
          ||this->HighlightPrimaryKeywords(&index,opts,p_chars)
          ||this->HighlightSecondaryKeywords(&index,opts,p_chars)
          ||this->HighlightString(&index,opts,c,p_chars)
          ||this->HighlightNumber(&index,opts,c,p_chars)
          ){
            continue;
        }
        this->_highlighting.push_back(HighlightingType::None);
        index+=1;
    }
    this->HighlightMatch(str);
    if (in_ml_comment && this->_content->substr(Util::SaturatingSub(this->_content->length(),2)) !="*/"){
        return true;
    }
    this->IsHighlighted = true;
    return false;
}


void Row::HighlightMatch(string word){
    if (word.empty()){
        return;
    }

    int index = 0;
    int searchMatch = this->Find(word,index,SearchDirection::Forward);
    while (searchMatch>=0){
        int next_index = searchMatch + word.length();
        if (next_index >= this->_content->length()){
            break;
        }
        for(int i=searchMatch+index;i<next_index;i++){
            this->_highlighting.at(i) = HighlightingType::Match;
        }
        index = next_index;
        searchMatch = this->Find(word,index,SearchDirection::Forward);
    }
}


bool Row::HighlightChar(int* index,HighlightingOptions opts,char c,const char* str){
    if (opts.Characters() && c == '\''){
        if ((*index)+1 < strlen(str)){
            char next_char = str[(*index)+1];
            int closingIndex = (*index)+2;
            if (next_char == '\\'){
                closingIndex = (*index) +3;
            }
            if (closingIndex <strlen(str)){
                char closing_char = str[closingIndex];
                if (closing_char == '\''){
                    int startIndex = (*index);
                    for(int i=0;i<=Util::SaturatingSub(closingIndex,startIndex );i++){
                        this->_highlighting.push_back(HighlightingType::Characters);
                        *index = *index+1;
                    }
                    return true;
                }
            }
        }
    }
    return false;
}


bool Row::HighlightComment(int* index,HighlightingOptions opts,char c,const char* str){
    if (opts.Comments() && c=='/' && (*index) <strlen(str)){
        if ((*index)+1 < strlen(str)){
            char next_char = str[(*index)+1];
            if (next_char == '/'){
                for(int i=*index;i<strlen(str);i++){
                    this->_highlighting.push_back(HighlightingType::Comments);
                    (*index) = (*index)+1;
                }
            }
        }
    }
    return false;
}


bool Row::HighlightString(int* index,HighlightingOptions opts,char c,const char* str){
    if (opts.Strings() && c == '"'){
        while(1){
            this->_highlighting.push_back(HighlightingType::String);
            (*index) = (*index)+1;
            if (strlen(str) <= *index){
               break; 
            }
            char next_char = str[*index];
            if (next_char == '"'){
                break;
            }
        }
        this->_highlighting.push_back(HighlightingType::String);
        *index = (*index)+1;
        return true;
    }
    return false;
}



bool Row::HighlightNumber(int* index,HighlightingOptions opts,char c,const char* str){
    if (opts.Numbers() && Util::IsAsciiDigit(c)){
        if (*index > 0){
            char prev_char = str[*index-1];
            if (!Util::IsSeperator(prev_char)){
                return false;
            }
        }
        while(1){
            this->_highlighting.push_back(HighlightingType::Number);
            *index = *index +1;
            if (*index >= strlen(str)){
                break;
            }
            char next_char = str[*index];
            if (next_char !='.' && !Util::IsAsciiDigit(next_char)){
                break;
            }             
        }
        return true;
    }
    return false;
}



bool Row::HighlightStr(int* index,string substring,const char* chars,HighlightingType hl_type){
    if (substring.empty()){
        return false;
    }

    for(int i=0;i<substring.length();i++){
        if (*index + i < strlen(chars)){
            char next_char = chars[(*index)+i];
            if (next_char != substring.at(i)){
                return false;
            }
        }else {
            return false;
        }
    }

    for(int i=0;i<substring.length();i++){
        this->_highlighting.push_back(hl_type);
        *index = (*index) +1;
    }
    return true;
}

bool Row::HighlightPrimaryKeywords(int* index,HighlightingOptions opts,const char* chars){
    if (*index > 0){
        char prev_char = chars[*index -1];
        if (!Util::IsSeperator(prev_char)){
            return false;
        }
    }

    vector<string> keywords = opts.PrimaryKeywords();
    for(int i=0;i<keywords.size();i++){
        string keyword = keywords[i];
        if (*index < Util::SaturatingSub(strlen(chars),keyword.length())){
            char next_char = chars[*index + keyword.length()];
            if (!Util::IsSeperator(next_char)){
                continue;
            }
        }

        if (this->HighlightStr(index,keyword,chars,HighlightingType::Keyword1)){
            return true;
        }
    }
    return false;
}


bool Row::HighlightSecondaryKeywords(int* index,HighlightingOptions opts,const char* chars){
    if (*index > 0){
        char prev_char = chars[*index -1];
        if (!Util::IsSeperator(prev_char)){
            return false;
        }
    }

    vector<string> keywords = opts.SecondaryKeywords();
    for(int i=0;i<keywords.size();i++){
        string keyword = keywords[i];
        if (*index < Util::SaturatingSub(strlen(chars),keyword.length())){
            char next_char = chars[*index + keyword.length()];
            if (!Util::IsSeperator(next_char)){
                continue;
            }
        }

        if (this->HighlightStr(index,keyword,chars,HighlightingType::Keyword2)){
            return true;
        }
    }
    return false;
}



bool Row::HighlightMultilineComments(int* index,HighlightingOptions opts,char c,const char* str){
    if (opts.Comments() && c=='/' && *index < strlen(str)){
        if ((*index + 1) < strlen(str)){
            char next_char = str[*index+1];
            if (next_char == '*'){
                int close_index = strlen(str);
                size_t find_index = this->_content->find("*/",*index+2);
                if (find_index != string::npos){
                   close_index = *index + (int)find_index + 4;
                }
                for(int i=*index;i<close_index;i++){
                    this->_highlighting.push_back(HighlightingType::MultilineComments);
                    *index = *index + 1;
                }
                return true;
            }
        }
    }
    return false;
}

